<?php
/**
 * Created by PhpStorm.
 * User: heitao
 * Date: 2018/5/10
 * Time: 14:44
 */

namespace manniu\model;

use think\Config;

class Redis
{
    public $redisObj = null;    // redis实例化时静态变量
    protected $sn;              // 实例名
    protected $index;           // 数据库

    /**
     * 构造方法
     * @access public
     * @param array $options 配置项
     */
    public function __construct($options = [])
    {
        if(!is_array($options) || count($options) < 1){
            $options = Config::get('cache');
        }
        $host = trim(isset($options["host"]) ? $options["host"] : '127.0.0.1');
        $port = trim(isset($options["port"]) ? $options["port"] : 6379);
        $auth = trim(isset($options["password"]) ? $options["password"] :"password");
        $index  = trim(isset($options["select"]) ? $options["select"] : 0);
        if (!is_integer($index) && $index>16) {
            $index = 0;
        }
        $sn = md5("{$host}{$port}{$auth}{$index}");
        $this->sn = $sn;
        if (!isset($this->redisObj[$this->sn])){
            $this->redisObj[$this->sn]=new \Redis();
            $this->redisObj[$this->sn]->connect($host,$port);
            $this->redisObj[$this->sn]->auth($auth);
            $this->redisObj[$this->sn]->select($index);
        }
        $this->redisObj[$this->sn]->sn=$sn;
        $this->index = $index;
        return ;
    }

    /**
     * 静态实例化方法
     * @param array $options 配置项
     * @return Redis
     */
    public static function instance($options=[])
    {
        return  new Redis($options);
    }

    /**
     * 设置key的过期时间
     * @param $key      键名
     * @param int $time 过期时间
     * @return bool
     */
    public function setExpire($key,$time = 0){
        if(!$key){
            return false;
        }
        switch (true){
            case ($time == 0):
                return $this->redisObj[$this->sn]->expire($key, 0);
                break;
            case ($time > time()):
                $this->redisObj[$this->sn]->expireAt($key, $time);
                break;
            default:
                return $this->redisObj[$this->sn]->expire($key, $time);
        }
    }

    /*------------------------------------start 1.string结构----------------------------------------------------*/
    /**
     * 增/改,string类型
     * @param string $key   键名
     * @param string $value 键值
     * @param int $timeOut  时间;0 - 表示无过期时间
     * @return true         【总是返回true】
     */
    public function set($key, $value, $timeOut = 0) {
        $setRes = $this->redisObj[$this->sn]->set($key, $value);
        if ($timeOut > 0){
            $this->redisObj[$this->sn]->expire($key, $timeOut);
        }
        return $setRes;
    }

    /**
     * 查询某键对应的值
     * @param $key 键名
     * @return bool|string 查询成功返回信息，失败返回false
     */
    public  function get($key){
        $setRes =  $this->redisObj[$this->sn]->get($key); //不存在返回false
        if($setRes === 'false'){
            return false;
        }
        return $setRes;
    }
    /*------------------------------------1.end string结构------------------------------------------------------*/

    /*------------------------------------2.start list结构------------------------------------------------------*/
    /**
     * 增，构建一个列表(先进后出，类似栈)
     * @param string $key   列表键名
     * @param string $value 元素
     * @param num $timeOut  过期时间
     */
    public function lPush($key, $value, $timeOut = 0){
        $re = $this->redisObj[$this->sn]->lPush($key, $value);
        if ($timeOut > 0){
            $this->redisObj[$this->sn]->expire($key, $timeOut);
        }
        return $re;
    }

    /**
     * 增，构建一个列表(先进先出，类似队列)
     * @param string $key   列表键名
     * @param string $value 元素
     * @param num $timeOut  过期时间
     */
    public function rPush($key, $value, $timeOut = 0){
        $re = $this->redisObj[$this->sn]->rPush($key, $value);
        if ($timeOut > 0){
            $this->redisObj[$this->sn]->expire($key, $timeOut);
        }
        return $re;
    }

    /**
     * 查，获取所有列表元素（从头到尾取）
     * @param string $key   键名
     * @param int $head     开始
     * @param int $tail     结束
     */
    public function lRanges($key, $head, $tail){
        return $this->redisObj[$this->sn]->lRange($key, $head, $tail);
    }

    /**
     * 移除并返回列表的第一个元素（从头移除）
     * @param string $key 键名
     * @return mixed
     */
    public function rPop($key){
        return $this->redisObj[$this->sn]->rPop($key);
    }

    /**
     * 移除并返回列表的第一个元素（从尾移除）
     * @param string $key 键名
     * @return mixed
     */
    public function lPop($key){
        return $this->redisObj[$this->sn]->lPop($key);
    }
    /*------------------------------------2.end list结构--------------------------------------------------------*/

    /*------------------------------------3.start set结构-------------------------------------------------------*/
    /**
     * 增，构建一个集合(无序集合)
     * @param string $key           集合键名
     * @param string|array $member  集合元素
     * @param int $timeOut          时间;0 - 表示无过期时间
     * @return mixed
     */
    public function sAdd($key, $member, $timeOut = 0){
        if(is_array($member)){
            $re = $this->redisObj[$this->sn]->sAddArray($key, $member);
        }else{
            $re = $this->redisObj[$this->sn]->sAdd($key, $member);
        }
        if ($timeOut > 0){
            $this->redisObj[$this->sn]->expire($key, $timeOut);
        }
        return $re;
    }

    /**
     * 查，返回集合中的所有的成员
     * @param string $key 集合键名
     * @return mixed
     */
    public function sMembers($key){
        $re = $this->redisObj[$this->sn]->exists($key); //存在返回1，不存在返回0
        if(!$re) {
            return false;
        };
        return $this->redisObj[$this->sn]->sMembers($key);
    }
    /*------------------------------------3.end set结构---------------------------------------------------------*/

    /*------------------------------------3.start sort set结构--------------------------------------------------*/
    /*
     * 增，构建一个集合(有序集合),支持批量写入,更新
     * @param string $key           有序集合键名
     * @param array $score_value    key为scoll, value为该权的值
     * @return int                  插入操作成功返回插入数量,【更新操作返回0】
     */
    public function zAdd($key, $score_value, $timeOut = 0, $json_encode = true){
        if(!is_array($score_value)) return false;
        $a = 0;//存放插入的数量
        foreach($score_value as $score => $value){
            $re = $this->redisObj[$this->sn]->zAdd($key, $score, $json_encode ? json_encode($value) : $value); //当修改时，可以修改，但不返回更新数量
            $re && $a+=1;
            if ($timeOut > 0) {
                $this->redisObj[$this->sn]->expire($key, $timeOut);
            }
        }
        return $a;
    }

    /**
     * 查，有序集合查询，可升序降序,默认从第一条开始，查询一条数据
     * @param string $key   有序集合键名
     * @param int $min      从第$min条开始
     * @param int $num      查询的条数
     * @param string $order asc - 表示升序排序；desc - 表示降序排序
     * @return array|bool   如果成功，返回查询信息，如果失败返回false
     */
    public function zRange($key, $min = 0 , $num = 1, $order = 'desc', $json_decode = true, $assoc = true){
        $re = $this->redisObj[$this->sn]->exists($key); //存在返回1，不存在返回0
        if(!$re){
            return false; //不存在键值
        }
        if('desc' == strtolower($order)){
            $re = $this->redisObj[$this->sn]->zRevRange($key, $min , $min+$num-1);
        }else{
            $re = $this->redisObj[$this->sn]->zRange($key, $min , $min+$num-1);
        }
        if(!$re){
            return false; //查询的范围值为空
        }

        if($json_decode){
            foreach ($re as $k => $item){
                $re[$k] = json_decode($item, $assoc);
            }
        }

        return $re;
    }

    /**
     * 返回集合key中，成员member的排名
     * @param string $key       有序集合键名
     * @param string $member    scroll值
     * @param string $type      asc - 表示升序排序,按score值递减(从大到小)顺序返回其排位；desc - 表示降序排序,score值递增(从小到大)顺序返回其排位
     * @return bool             键值不存在返回false，存在返回其排名下标
     */
    public function zRank($key, $member, $type = 'desc'){
        $type = strtolower(trim($type));
        if($type == 'desc'){
            $re = $this->redisObj[$this->sn]->zRevRank($key, $member);
        }else{
            $re = $this->redisObj[$this->sn]->zRank($key, $member);
        }
        if(!is_numeric($re)){
            return false; //不存在键值
        }
        return $re;
    }

    /**
     * 返回名称为key的zset中score >= star且score <= end的所有元素
     * @param string $key   有序集合键名
     * @param int $star     大于等于$star的score
     * @param int $end      小于等于$star的score
     * @return array        条件区间的所有元素
     */
    public function zRangeByScore($key, $star, $end){
        return $this->redisObj[$this->sn]->zRangeByScore($key, $star, $end);
    }

    /**
     * 返回名称为key的zset中元素member的score
     * @param string $key       有序集合键名
     * @param string $member    score对应的值
     * @return string           返回查询的member值
     */
    function zScore($key, $member){
        return $this->redisObj[$this->sn]->zScore($key, $member);
    }
    /*------------------------------------3.end sort set结构----------------------------------------------------*/

    /*------------------------------------3.start hash结构------------------------------------------------------*/
    /**
     * 增，构建一个hash类型
     * @param string $key       键名
     * @param string $fields    field（相当于键名）
     * @param string $value     value（相当于键值）
     * @param int $timeOut      过期时间
     * @return mixed
     */
    public function hSet($key, $fields, $value, $timeOut = 0){
        $re = $this->redisObj[$this->sn]->hSet($key, $fields, $value);
        if ($timeOut > 0){
            $this->redisObj[$this->sn]->expire($key, $timeOut);
        }
        return $re;
    }

    /**
     * 批量增，构建一个hash类型
     * @param string $key           键名
     * @param array $fields         一维数组, field -> value
     * @param int $timeOut          过期时间
     * @param boolean $json_encode  json编码
     * @return $number              返回OK【更新和插入操作都返回ok】
     */
    public function hMset($key, array $fields, $timeOut = 0, $json_encode = true){
        if($json_encode){
            $fields = array_map('json_encode', $fields);
        }
        $re = $this->redisObj[$this->sn]->hMset($key, $fields);
        if ($timeOut > 0){
            $this->redisObj[$this->sn]->expire($key, $timeOut);
        }
        return $re;
    }

    /**
     * 获取某个hash表的field值
     * @param string $key               键名
     * @param $filed null|string|array  为空时查询全部，为字符串时查询单个filed，为数组时查询条件为数组中所有元素
     * @param boolean $json_decode      json解码
     * @param boolean $assoc            返回类型;true返回数组;false返回对象
     * @return bool|string
     */
    public function hGet($key, $filed, $json_decode = true, $assoc = true){
        if(empty($filed)){
            $re = $this->redisObj[$this->sn]->hGetAll($key);
        }elseif(is_string($filed)){
            $re = $this->redisObj[$this->sn]->hGet($key, $filed);
        }elseif(is_array($filed)){
            $re = $this->redisObj[$this->sn]->hMGet($key, $filed);
        }
        if(!$re){
            return false;
        }
        if($json_decode){
            foreach ($re as $k => $item){
                $re[$k] = json_decode($item, $assoc);
            }
        }
        return $re;
    }

    /**
     * 获取某个hash表的所有值
     * @param string $key   键名
     * @param bool $combine 表示是否同时获取field
     * @return array        成功返回查询的数组信息，不存在信息返回false
     */
    public function hVals($key, $combine = false){
        $re = $this->redisObj[$this->sn]->exists($key); //存在返回1，不存在返回0
        if(!$re){
            return false;
        }
        $re = $this->redisObj[$this->sn]->hVals($key);
        if($combine === true){
            $keys = $this->redisObj[$this->sn]->hKeys($key);
            $re = array_combine($keys, $re);
            foreach($re as $k=>$v){
                if(!is_null(json_decode($v))){
                    $re[$k] = json_decode($v,true); //true表示把json返回成数组
                }
            }
        }
        return $re;
    }

    /**
     * 获取某个hash表的所有field
     * @param string $key   hash键名
     * @return array        成功返回查询的数组信息，不存在信息返回false
     */
    public function hKeys($key){
        $re = $this->redisObj[$this->sn]->exists($key); //存在返回1，不存在返回0
        if(!$re){
            return false;
        }
        $re = $this->redisObj[$this->sn]->hKeys($key);
        return $re;
    }
    /*------------------------------------3.end hash结构--------------------------------------------------------*/

    /*------------------------------------删除相关---------------------------------------------------------------*/
    /**
     * 删除key/field
     * @param string|array $key 键名
     * @param $type             类型，默认为常规，还有hash,zset
     * @param string $field     hash=>表示$field值；set=>表示value；zset=>表示value值；list类型特殊暂时不加
     * @return int |返回删除的个数
     */
    public function delete($key, $type = 'default', $field = ''){
        switch(strtolower(trim($type))){
            case 'hash':
                $re = $this->redisObj[$this->sn]->hDel($key, $field); //返回删除个数
                break;
            case 'set':
                $re = $this->redisObj[$this->sn]->sRem($key, $field); //返回删除个数
                break;
            case 'zset':
                $re = $this->redisObj[$this->sn]->zDelete($key, $field); //返回删除个数
                break;
            default:
                if(method_exists($this->redisObj[$this->sn], 'unLink')){
                    $re = $this->redisObj[$this->sn]->unLink($key); // Number of keys deleted
                }else{
                    $re = $this->redisObj[$this->sn]->del($key); // Number of keys deleted
                }
        }
        return $re;
    }

    /**
     * 清除数据库
     * @param int $type 默认为0，清除当前数据库；1表示清除所有数据库
     */
    function flush($type = 0){
        if($type) {
            $this->redisObj[$this->sn]->flushAll(); //清除所有数据库
        }else{
            $this->redisObj[$this->sn]->flushDB(); //清除当前数据库
        }
    }
    /*------------------------------------删除相关---------------------------------------------------------------*/

    /*------------------------------------其它------------------------------------------------------------------*/
    /**
     * 设置自增/自减功能
     * @param string $key       要改变的键值
     * @param int $num          改变的幅度，默认为1
     * @param string $member    类型是zset或hash，需要在输入member或filed字段
     * @param string $type      类型，default为普通增减,还有:zset,hash
     * @return bool|int         成功返回自增后的scroll整数，失败返回false
     */
    public function incre($key, $num = 1, $member = '', $type = 'default'){
        $num = intval($num);
        switch(strtolower(trim($type))){
            case "zset":
                $re = $this->redisObj[$this->sn]->zIncrBy($key, $num, $member); //增长权值
                break;
            case "hash":
                $re = $this->redisObj[$this->sn]->hIncrBy($key, $member, $num); //增长hashmap里的值
                break;
            default:
                if($num > 0){
                    $re = $this->redisObj[$this->sn]->incrBy($key, $num); //默认增长
                }else{
                    $re = $this->redisObj[$this->sn]->decrBy($key, -$num); //默认增长
                }
                break;
        }
        if($re){
            return $re;
        }
        return false;
    }

    /**
     * 检验某个键值是否存在
     * @param $keys         键名
     * @param string $type  类型，默认为常规
     * @param string $field 若为hash类型，输入$field
     * @return bool
     */
    public function exists($keys, $type = 'default', $field = ''){
        switch(strtolower(trim($type))){
            case 'hash':
                $re = $this->redisObj[$this->sn]->hExists($keys, $field); //有返回1，无返回0
                break;
            default:
                $re = $this->redisObj[$this->sn]->exists($keys);
                break;
        }
        return $re;
    }

    /**
     * 魔术方法 有不存在的操作的时候执行
     * @access public
     * @param string $method    方法名
     * @param array $args       参数
     * @return mixed
     */
    public function __call($method, $args)
    {
        return call_user_func_array([$this->redisObj[$this->sn], $method], $args);
    }
    /*------------------------------------其它------------------------------------------------------------------*/
}